xunit testing c# explained with code example

您所在的位置:网站首页 we learn unit test xunit testing c# explained with code example

xunit testing c# explained with code example

2024-07-14 22:46| 来源: 网络整理| 查看: 265

Unit Testing in C# with xUnit: Complete guideBob Code

Bob Code

·

Follow

25 min read·Apr 11, 2024

--

You just finished coding your new feature, it’s great and it works!

But how do you prove that it actually works?

Time to write your unit tests!

Bob Code OriginalsAgenda

I. Introduction- What are unit tests in .Net?- Why do we have to create unit tests?- What unit tests tools are there in .Net?

II. Getting Started- Creating a xUnit Test project- NuGet Package- Why do unit test classes need to be public?- Test Method Naming Convention- What does the [Fact] attribute do?- How to build xUnit test methods?

III. xUnit Data Annotations and Attributes- Data Driven Tests with [Theory]- [Trait]

IV. Mock- Mock behaviour

V. AAA: Arrange- SetUp- Return- Exceptions- Implicit checks

VI. AAA: Act- Fixture

VII. AAA: Assert

VIII. Running Test- Manually- Automatically- Code Coverage- Debugging- Running Tests in Parallel

IX. Best Practices

X. Documentation & Error List

I. IntroductionWhat are unit tests in .Net?

Unit tests refer to testing “unit of works”, which basically means:

Code that has no external dependencies (isolated)

Unit testing is a software testing methodology where individual units or components of a software application are tested in isolation to ensure they function correctly.

In comparison integration test 2 or more components that interact together (not isolated).

End to End Test go even further and test an app through its UI# with tools like Selenium

Testing in .NET - .NETThis article gives a brief overview of testing concepts, terminology, and tools for testing in .NET.

learn.microsoft.com

Why do we have to create Unit tests?

Like do we really need to go through this? If it works with manual testing, then why even write unit tests?

Are there any developers who actually enjoy writing these things?

Isn’t it why we have testers?

Bob Code Originals

Prove that it works

Sure, you can go debug mode, input some fake data and do a demo to the whole team to prove that it works.

But that’s not very time efficient, persuasive and a long term solution.

Isn’t it convenient to have all these passing tests showing to yourself and everyone else that the code works?

Regression

Basically, not only do you want to make sure that your current code works but also, your app is quite likely to change overtime and therefore you want to make sure that its other functionalities keep working as expected.

So that if you add or change code, the tests will always check whether the other functionalities you created before still work as they should.

Automation

You don’t want to manually have to manual test all the the time. That’s when tests come in handy, especially when automatically run in a pipeline!

What unit tests tools are there in .Net?xUnit

xUnit is a free, open-source, community-focused unit testing tool for .NET.

Its frameworks provide a framework for writing and running automated unit tests.

It is a project of the .NET Foundation. xUnit.net is the latest technology for unit testing .NET apps. It is free and open source.

xUnit is now the Microsoft default and standard testing tool in Visual Studio for .Net Core.

A nice feature of xUnit is the ability to test code from

.Net Framework.Net CoreXamarin

This blog focuses on xUnit :)

Comparing xUnit.net to other frameworksDocumentation site for the xUnit.net unit testing framework

xunit.net

Unit testing C# code in .NET using dotnet test and xUnit - .NETLearn unit test concepts in C# and .NET through an interactive experience building a sample solution step-by-step using…

learn.microsoft.com

N Unit

NUnit is a unit-testing framework for all .NET languages

MSTest

MSTest is the Microsoft test framework for all .NET languages

Now that we understand the key concepts, let’s create a xUnit test project!

II. Getting StartedBob Code OriginalsCreating a xUnit Test project

Naming the test project:

Name of project + .UnitTests

Keep the global using.cs that will ensure that Xunit is used everywhere

NuGet Package

If you create a xUnit project from VS, the package should already be added, otherwise you can manually add it

And you will need this package to run tests in VS: xunit.runner.visualstudio

Naming the file:

Name of cs file to be tested + Tests, e.g. BrandControllerTests

Why do unit test classes need to be public?

In C#, unit tests typically need to be declared as public because they need to be accessible by the testing framework.

When you write unit tests using frameworks like NUnit, MSTest, xUnit, etc., these frameworks require that the test methods be public so that they can be discovered and executed.

Test Method Naming Convention

First thing first, a test method needs to be named right, it should consist of three parts:

The name of the method being tested.The scenario under which it’s being tested.The expected behaviour when the scenario is invoked.

nameOfMethodBeingTested_Scenario_ExpectedBehaviour()

example, original method to be tested

public async Task GetAllBrands(){}

Test Method (add [Fact] on top)

[Fact]public async Task GetAllBrands_ActionExecutes_CheckResultType_ReturnsBrand_DTOs(){}

Another naming convention is to use

_should__when_

Example: Constructor_should_throw_when_parameters_are_null

What does the [Fact] attribute do?[Fact]

The [Fact] attribute declares a test method that’s run by the test runner.

Therefore it must be added on top of each test method like below

[Fact] // => [fact] will make sure that the test is picked up by the test runnerpublic async Task GetAllBrands_ActionExecutes_CheckResultType_ReturnsBrand_DTOs(){}How to build xUnit tests? Using Moq and AAA

There are basically 4 steps into building a test:

1/ Mock external dependencies2/ Arrange3/ Act4/ Assert

1/ Mocking

So how to deal with dependencies? Well, we don’t! We simply simulate them by using a framework called Mock

Dealing with dependencies | Unit Testing in C#Most components rely on dependencies to perform their tasks. Delegating certain concerns to dependencies makes it…

docs.educationsmediagroup.com

First, you will need to install the package Moq

So what does Mocking do?

Let’s look at the below class

public class BrandController : ControllerBase{ private readonly IBrandService _service;

public BrandController(IBrandService service, ILogger logger) { _service = service; }

You can see that it takes one external dependencies

_service

However, a unit test should not rely on external objects.

Therefore we need to stimulate the behaviour of this object.

To do so, we will mock it, like so:

public class BrandControllerTests { private readonly Mock _serviceMock;

public BrandControllerTests() { _serviceMock = new Mock(MockBehavior.Strict); }

https://www.soapui.org/learn/mocking/what-is-api-mocking/

Now what we have our simulated object, so we can use it and its methods in our test code.

This is the method we want to test

public async Task GetBrandByID(int id){ return Ok(await _service.GetByIdAsync(id));}

The method GetBrandByID in the BrandController uses the _service (that we just mocked) that:

takes as input an intreturns an object

GetBrandByID then returns an OkResult

2/ Arrange takes care of arranging the Mock object so it

calls the method passed in the class we are testing, using SetUpreturns the right object using Return// Arrange _serviceMock.Setup(x => x.GetByIdAsync(_id)) .ReturnsAsync(new Brand_DTO { ID = _id });Bob Code Originals

3/ Act will actually call the method we are testing passing the mock object(s) to the class under test

// Act var controller = new BrandController(_serviceMock.Object); var result = await controller.GetBrandByID(_id);

4/ Assert is when we check whether the test returns the expected value, in our test example we expect:

An OkResultThat it returns a object of type Brand_DTOThat the integer id we pass as parameter is correct// Assert var okResult = Assert.IsType(result.Result); var item = Assert.IsType(okResult.Value); Assert.Equal(_id, item.ID);Source: https://methodpoet.com/

And here is our complete xUnit test class!

public class BrandControllerTests { private readonly Mock _serviceMock;

public BrandControllerTests() { _serviceMock = new Mock(MockBehavior.Strict); }

[Fact] public async Task GetByID_Returns_SingleObject() { // Arrange _serviceMock.Setup(x => x.GetByIdAsync(_id)) .ReturnsAsync(new ProductModelInClubCollection_DTO { ID = _id });

// Act var controller = new ProductModelInClubCollectionController(_serviceMock.Object, _loggerMock.Object, _mapperMock.Object); var result = await controller.GetByID(_id);

// Assert var okResult = Assert.IsType(result.Result); var item = Assert.IsType(okResult.Value); Assert.Equal(_id, item.ID); }

And that’s it!

Now let’s delve deeper into each part of xUnit and look at multiple scenarios

III. xUnit Data Annotations and AttributesData Driven Tests with [Theory]

Theory enables you to add multiple inputs to the test one method.

This is called Data Driven Tests

So in the below method we are passing 3 inputs to one test method

[Theory][InlineData(-1)][InlineData(0)][InlineData(1)]public void IsPrime_ValuesLessThan2_ReturnFalse(int value){ var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");}Bob Code Originals

The limitation of [InlineData] is that we can only apply it to one test method.

If you wish to share input data amongst classes use the [ClassData] attribute instead

[Theory][ClassData(typeof(Input))]public void IsPrime_ValuesLessThan2_ReturnFalse(int value){ var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");}

OR [MemberData]

[Theory][MemberData(nameof(Input)]public void IsPrime_ValuesLessThan2_ReturnFalse(int value){ var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");}

For this you will need another class

public class Input: IEnumerable{ public IEnumerator GetEnumerator() { yield return new object[] { 1, 2, 3 }; }

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();}

Creating parameterised tests in xUnit with [InlineData], [ClassData], and [MemberData]In this post I describe how to create parameterised tests using xUnit's [Theory], [InlineData], [ClassData], and…

andrewlock.net

Alternatively you can also create your own attribute

public class Input: DataAttribute{ public override IEnumerator GetData(MethodInfo testMethod) { yield return new object[] { 1, 2, 3 }; }}

Now we can use this custom attribute in our test

[Theory][Input]public void IsPrime_ValuesLessThan2_ReturnFalse(int value){ var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");}

These techniques might seem cumbersome, thankfully there is another option: TheoryData

First we use ClassData

[Theory][ClassData(typeof(Input))]public void IsPrime_ValuesLessThan2_ReturnFalse(int value){ var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");}

Then we create a TheoryData class that’s way easier to set-up

public class Input: TheoryData{ public Input() { Add(-1); Add(0); Add(3); }}Creating Data-Driven Tests With xUnitData-driven testing is a testing method where test data is provided through some external source. Hence it's also known…

www.milanjovanovic.tech

[Trait]

Useful for filtering and marking the type or group of tests, here:

The trait type is categorythe category name is integration[Fact, Trait("Category", "Integration")]public async Task SendInactiveMember_Should_CreateTicket(){}

This is particularly useful to differenciate between test types (unit, integration, UI…)

[Fact][Trait("Category","Unit")]public void Test1(){ ...}

[Fact][Trait("Category","Integration")]public void Test2(){ ...}

[Fact][Trait("Category","UI")]public void Test3(){ ...}

When would that be useful? Well, in pipelines for example!

You can now filter tests (credits: dateo-software.de)

# Run all unit testsdotnet test --filter "(FullyQualifiedName!=Integration.Tests)&(FullyQualifiedName!=System.Tests)"

# Run all integration testsdotnet test --filter "(FullyQualifiedName=Integration.Tests)&(Category=ReadyForProduction)"

Improve your pipeline maintainability with test categories in xUnitToday let's explore how you can improve the stability, maintainability, and readability of your CI and CD pipelines…

dateo-software.de

Organizing Tests With xUnit TraitsOnce you start to have a larger number of tests it can be important to be able to break them down into different…

www.brendanconnolly.net

IV. MockMock behaviour

When creating Mocks, sometimes you want to make sure that the setup is exactly as it should, otherwise an error or exception should be thrown.

That’s when you want to use MockBehavior.Strict

If not and it doesn’t really matter whether the object behaves exactly as in real-life, such as with ILogger, then you can either keep as default or explicitly tag it as MockBehavior.Loose (the default behaviour)

public class BrandControllerTests { private readonly Mock _serviceMock; private readonly Mock _loggerMock;

public BrandControllerTests() { _serviceMock = new Mock(MockBehavior.Strict); _loggerMock = new Mock(MockBehavior.Loose); }

Strict

With strict mocks, the mock object will strictly enforce that only the expected interactions are allowed. Any unexpected calls to methods that haven’t been explicitly set up will result in a test failure.

Loose

Loose mocks, on the other hand, allow unexpected method calls to pass without failing the test. They are more permissive and lenient compared to strict mocks.

Bob Code OriginalsMock customization | Unit Testing in C#Moq has certain behaviors that might appear quite opinionated. Although these behaviors are active by default, they can…

docs.educationsmediagroup.com

V. AAA: Arrange

Remember every external dependencies Mock object?

If any is passed in your original method, you will need to arrange them, aka set them up.

The SetUp part is perhaps the most important set in your whole test! If you don’t exactly (like exactly) simulate the original method, with the exact same dependencies and return type, you will get this error:

Moq.MockException : invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.

Bob Code Originals

Let’s look at this example, this is the original method

public async Task GetByID(int id){ return Ok(await _service.GetByIdAsync(id));}

You can see that it uses one object:

_service

The test method will need the object to be mocked, i.e. simulated

So we need to simulate the _service object to return a ClubCollectionGetModel

SetUpBob Code Originals

Let’s set it up!

// Arrange_serviceMock.Setup(x => x.GetByIdAsync(It.Is())) .ReturnsAsync(new ProductModelInClubCollection_DTO { ID = _id });

This is how we create a setup:

_objectMocked .SetUp

(x => x.MethodUsed (Optional: the argument or type passed to the MethodUsed (e.g. It.Is, or simply 1))).Return (the object or type want to be returned)

Here are some examples visualised

Bob Code Originals

See all SetUp Options in the Moq documentation below

MoqSetupProperty(TProperty) Method (Expression(Func))

documentation.help

https://documentation.help/Moq/Results | Unit Testing in C#When configuring mocks, it is important to specify the return value of functions (methods that return a value) and…

docs.educationsmediagroup.com

Let’s look at some parameter examples that you can pass in the SetUp Method being mocked:

It.IsAny()1It.Is()It.Is(i => i % 2 == 0)It.IsInRange(0, 10, Range.Inclusive)It.IsRegex(“[a-d]+”, RegexOptions.IgnoreCase)

Here’s a compiled list of several setup and return options!

// Any integer, any value returned_serviceMock.Setup(x => x.GetByIdAsync(It.IsAny())) .Returns(objecttobereturned)

// hardcoded value_serviceMock.Setup(x => x.GetByIdAsync(1)) .Returns(objecttobereturned)

// When checking for one specific value to be returned_serviceMock.Setup(x => x.GetByIdAsync(It.Is()) .Returns(objecttobereturned)

// Can include specific matching pattern_serviceMock.Setup(x => x.GetByIdAsync(It.Is(i => i % 2 == 0))) .Returns(objecttobereturned)

// Matching ranges_serviceMock.Setup(x => x.GetByIdAsync(It.IsInRange(0, 10, Range.Inclusive))) .Returns(objecttobereturned)

// Matching regex_serviceMock.Setup(x => x.GetByIdAsync(It.IsRegex("[a-d]+", RegexOptions.IgnoreCase))) .Returns(objecttobereturned)

// Matching complex condition_serviceMock.Setup(x => x.Method(It.Is(i => i % 2 == 0 && i > 0))) .Returns(objectToBeReturned);

// Callback example_serviceMock.Setup(x => x.Method(It.IsAny())) .Callback(value => Console.WriteLine($"Called with value: {value}"));

// Throws exception_serviceMock.Setup(x => x.Method(It.IsAny())) .Throws();

// Throws exception with custom message_serviceMock.Setup(x => x.Method(It.IsAny())) .Throws(new Exception("Custom exception message"));

// Sets up multiple return values, will return 'result1' on the first call, and 'result2' on subsequent calls_serviceMock.SetupSequence(x => x.Method(It.IsAny())) .Returns("result1").Returns("result2");

// Setup with multiple arguments_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.IsAny(), It.IsAny())) .Returns(objectToBeReturned);

// Setup with multiple arguments and specific conditions_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.Is(i => i > 0), It.Is(s => s.Length > 5))) .Returns(objectToBeReturned);

// Setup with multiple arguments and specific conditions for one argument and any value for the other_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.Is(i => i > 0), It.IsAny())) .Returns(objectToBeReturned);

// Setup with custom matching using Match class_serviceMock.Setup(x => x.MethodWithCustomMatcher(Match.Create(i => i % 2 == 0))) .Returns(objectToBeReturned);

// Setup with custom return value based on argument values_serviceMock.Setup(x => x.MethodWithCustomReturnValue(It.IsAny())) .Returns((int arg) => arg % 2 == 0 ? "Even" : "Odd");

// Setup with asynchronous return value using Task.FromResult_serviceMock.Setup(x => x.AsyncMethod()) .ReturnsAsync(Task.FromResult(objectToBeReturned));

// Setup with asynchronous return value using Task.FromResult with delay_serviceMock.Setup(x => x.AsyncMethodWithDelay()).ReturnsAsync(async () =>{ await Task.Delay(100); return objectToBeReturned;});

// Setup with asynchronous return value using async lambda_serviceMock.Setup(async x => await x.AsyncMethod()) .Returns(objectToBeReturned);

// Setup with throwing an exception asynchronously_serviceMock.Setup(x => x.AsyncMethod()) .ThrowsAsync(new Exception("Async exception"));

// Setup with custom behavior using a function_serviceMock.Setup(x => x.MethodWithCustomBehavior(It.IsAny())) .Returns((int arg) =>{ if (arg < 0) throw new ArgumentException("Argument cannot be negative"); return objectToBeReturned;});

Moq: How to specify the argument with detail condition when mock a method?This is my own note to remember how we can pass argument to mocked method in various ways. … Tagged with unittest…

dev.to

Return

Returns or ReturnsAsync and The Object(s) you want to return

// Returns_serviceMock.Setup(x => x.GetByIdAsync(1).Returns(objecttobereturned)

// Returns Async_serviceMock.Setup(x => x.GetByIdAsync(_id)).ReturnsAsync(objecttobereturned)

// Or use Task.FromResult instead of ReturnsAsync_serviceMock.Setup(x => x.GetByIdAsync(1).Returns(Task.FromResult(objecttobereturned))

Bob Code Originals

Return for void/task

What about methods that actually don’t return anything?

See this example, we actually just perform a Task and only return an Ok (ActionResult), but the _service doesn’t return anything

public async Task Delete(int id){ await _service.DeleteAsync(id); return Ok();}

In this case we just return Task.CompletedTask

// For tasks: Task.CompletedTask_serviceMock.Setup(x => x.DeleteAsync(_id)).Returns(Task.CompletedTask);

// For void: Callback_serviceMock.Setup(x => x.DeleteAsync(_id)).Callback(() => { });

Callbacks | Unit Testing in C#A powerful capability of Moq is to attach custom code to configured methods and properties' getters and setters. This…

docs.educationsmediagroup.com

Return for null

What about methods that should return null (e.g. for testing null exceptions)?

// null string mock.Setup(p => p.Property).Returns(null as string);

// null object_mock.Setup(p => p.GetProductmodel(id).ReturnsAsync((ProductmodelResponseModel)null);

Results | Unit Testing in C#When configuring mocks, it is important to specify the return value of functions (methods that return a value) and…

docs.educationsmediagroup.com

Throwing exception

Sometime a method block throws an exception, so instead of returns we use .Throws

mock.Setup(p => p.DoSomething()) .Throws(new Exception("My custom exception"));

// Asyncmock.Setup(p => p.DoSomething()) .ThrowsAsync(new Exception("My custom exception"));

Exceptions | Unit Testing in C#One of the most common tasks that were solved with callbacks is throwing an exception when a certain method is invoked…

docs.educationsmediagroup.com

Unit Testing Exceptions in C#Using C#, .NET Core, and xUnit, we will dive into how to unit test exceptions using xUnit (and other testing frameworks…

chadgolden.com

Using methods from the base class

What if your class uses a base, like so

public class BrandInClubAssortmentService : GenericService, IBrandInClubAssortmentService{ private readonly IProductAndPricingAPIClient _productAndPricingAPIClient;

public BrandInClubAssortmentService( IProductAndPricingAPIClient productAndPricingAPIClient, IMapper mapper) : base(mapper) { _productAndPricingAPIClient = productAndPricingAPIClient; }

You’d need to Mock the mapper and logger to ensure good functioning of the class, and also pass them as Mock.Object in your class constructor.

var service = new BrandInClubAssortmentService( ProductAndPricingAPIClientMock.Object, MapperMock.Object,);

Otherwise you are quite likely to encounter the below exception

Moq.MockException : IMapperBase.Map(BrandInClubAssortment_DTO) invocation failed with mock behavior Strict.All invocations on the mock must have a corresponding setup.

What if, even though you have set-up the base mock objects correctly, you still get this error?

If you look at the documentation, then you will see the recommendation to use .CallBase()

mock.Setup(p => p.Greet()).CallBase();Base class | Unit Testing in C#Especially in older codebases, services and components can be part of a deep hierarchy of classes. Often, belonging to…

docs.educationsmediagroup.com

However, this only works if the mock is created from a class, not an interface, and the method being called is not abstract. So, this won’t work

_fixture.MapperMock.Setup(x => x.Map(It.IsAny())).CallBase();

If the Mock is as follows

MapperMock = new Mock(MockBehavior.Strict);

The solution is to set it up as a normal mock

_fixture.MapperMock.Setup(x => x.Map(It.IsAny())) .Returns(new BrandInClubAssortment());Implicit checks

The above is for explicit verification aka, we pass a method and expect a hardcoded object, the other way of doing this is just checking whether the method works regardless of what it returns using:

VerifiableVerifyAll// Implicit uses .Verifiable_serviceMock.Setup(p => p.GetByIdAsync(It.IsAny())).Verifiable();

// In the Assert part we use_serviceMock.VerifyAll();

// OR to check multiple mocksMock.Verify(_serviceMock1, _serviceMock2);

Verifications | Unit Testing in C#When writing unit tests for components consuming dependencies, it’s important to make sure the right calls were invoked…

docs.educationsmediagroup.com

VI. AAA: Act

Once you have all dependencies setup, now it’s time to actually instantiate your class that you are testing

Bob Code Originals

The trick is to pass the objects of the Mocks instead of the external dependencies to your class.

// Act

# First we instantiate the class with Mock objectsvar controller = new ProductModelInClubCollectionController(_serviceMock.Object);

# Then we actually call the method we want to testvar result = await controller.GetAll();

Finally, it’s time to check if the method returns what we expect, we do this in the Assert

Fixture

What is the IFixture class in c# and how to use it with xUnit?

Since you will most likely end-up with a lot of tests, instantiating a new object for each test method might not be the best in terms of resources.

One potential solution would be to instantiate your object in the constructor

public class ProductModelInClubCollectionServiceTests{ private readonly Mock _ccRepositoryMock;

private readonly ProductModelInClubCollectionService _service;

public ProductModelInClubCollectionServiceTests() { _ccRepositoryMock = new Mock(MockBehavior.Strict);

_service = new ProductModelInClubCollectionService(_ccRepositoryMock.ObjectcRepositoryMock.Object); }

Even if you pass the object (example above “controller”) in the constructor of the test class, which will help in terms of code size, the amount of objects created will be the same.

This calls for the need of another solution, which is the fixture!

However, most documentation provides examples that don’t include any external objects like so

public class StackTests : IDisposable{ Stack stack;

public StackTests() { stack = new Stack(); }

public void Dispose() { stack.Dispose(); }

[Fact] public void WithNoItems_CountShouldReturnZero() { var count = stack.Count;

Assert.Equal(0, count); }

In real life scenarios you’re quite likely to have several mock.Object within your constructor!

So let’s see how to go about it :)

The trick is to add the Mocks in the Fixture

public class BrandInClubAssortmentServiceFixture : IDisposable{ public Mock ClubCollectionRepositoryMock { get; }

public BrandInClubAssortmentService brandInClubAssortmentFixture { get; }

public BrandInClubAssortmentServiceFixture() { ClubCollectionRepositoryMock = new Mock(MockBehavior.Strict);

var service = new BrandInClubAssortmentService( ClubCollectionRepositoryMock.Object, );

brandInClubAssortmentFixture = service; }

public void Dispose() {

}}

Yes as you can see above you need to add all the mock manually, you might think that it is duplicating the original Mock in your test class but let’s see what comes next ;)

Let’s consider the original code

[Fact] public async Task AddAsync_WhenBrandDoesNotExist_ThrowsPlutusBusinessRuleViolationException() { // Arrange var service = new BrandInClubAssortmentService(_ccRepositoryMock.Object); var dto = new BrandInClubAssortment_DTO { ClubCollectionID = 1, BrandName = "brand" }; _ccRepositoryMock.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(new ClubCollection());

// Act async Task Act() => await service.AddAsync(dto);

// Assert await Assert.ThrowsAsync(Act); }

Now, we need to add the fixture to our code

[Fact]public async Task AddAsync_WhenBrandDoesNotExist_ThrowsPlutusBusinessRuleViolationException(){ // Arrange var dto = new BrandInClubAssortment_DTO { ClubCollectionID = 1, BrandName = "brand" }; _fixture.ClubCollectionRepositoryMock.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(new ClubCollection()); _fixture.ProductAndPricingAPIClientMock.Setup(x => x.GetBrand(It.IsAny())).ReturnsAsync((BrandResponseModel)null);

// Act async Task Act() => await _fixture.brandInClubAssortmentFixture.AddAsync(dto);

// Assert await Assert.ThrowsAsync(Act);}

We now set up the fixture Mocks and then we use the _fixture to directly use the method.

So in the test constructor, we actually don’t need any Mock anymore!

Initially we had this constructor for the test

public class BrandInClubAssortmentServiceTests : IClassFixture { private readonly Mock _ccRepositoryMock;

private readonly BrandInClubAssortmentServiceFixture _fixture;

public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture) { _ccRepositoryMock = new Mock(MockBehavior.Strict);

_fixture = fixture; }

But now we can just keep the _fixture :)

public class BrandInClubAssortmentServiceTests : IClassFixture { private readonly BrandInClubAssortmentServiceFixture _fixture;

public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture) { _fixture = fixture; }

Using Fixture amongst several classes

Now, let’s image that several of our classes actually use the _fixture.

What we would need to do is:

#1 Add the [Collection] attribute to all classes that use the _fixture

You can actually remove the following! : IClassFixture as [Collection] is now enough to use Fixture

[Collection("ServiceCollectionName")]public class BrandInClubAssortmentServiceTests { private readonly BrandInClubAssortmentServiceFixture _fixture;

public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture) { _fixture = fixture; }

#2 Create a collection class that

Has the attribute[CollectionDefinition] with the matching name in the other classesInherits from ICollectionFixture[CollectionDefinition("BrandInClubAssortmentServiceTests")]public class BrandInClubAssortmentServiceCollection : ICollectionFixture{}Shared Context between TestsDocumentation site for the xUnit.net unit testing framework

xunit.net

Whether fixture is a feature you want to use is up to you.

I believe there are certain advantages of using the Fixture feature:

Object Management:

xUnit.net creates a new instance of the test class for every test. When using a class fixture, xUnit.net will ensure that the fixture instance will be created before any of the tests have run, and once all the tests have finished, it will clean up the fixture object by calling Dispose, if present.

Code Reusability:

Set up your object once and reuse it everywhere in your test classes

Shared Context between TestsDocumentation site for the xUnit.net unit testing framework

xunit.net

XUnit - Part 5: Share Test Context With IClassFixture and ICollectionFixture - Hamid MosallaThis post is another post in xUnit Series. We see how we can use IClassFixture and ICollectionFixture to share test…

hamidmosalla.com

Harnessing Class Fixtures in xUnitAs developers navigating the realm of unit testing with xUnit, one of the valuable tools in our... Tagged with dotnet…

dev.to

7. Assert

In assert we check whether the test performs as it should.

This is a critical step as there are many things we can check, this where you want to cover all the paths possible.

Here are some examples

Check whether the result returns anything (e.g. notnull or null)Check whether the result returns the right values, types and amount of valuesCheck whether the methods used to come to the result have been called

Single Object

// Result Assert.IsType(result.Result);

// TypeAssert.IsType(okResult.Value);

Assert.IsType(typeof(testingclass), actualClass);Assert.IsType(actualClass);

// Value: .Equal(expected, actual) OR .NotEqual(expected, actual)Assert.Equal(_id, item.ID);

// Null/NotNullAssert.NotNull(item.ID);

// False/TrueAssert.False(string.IsNullOrEmpty(result.ExpectedString);

// VoidloginServiceMock.Verify(x => x.Logout(), Times.Once);

// Matches(expected, actual)Assert.Matches("ExpectedString", result.ExpectedString);

Strings

// Specific assertionAssert.Equal("abc", result, ignoreCase: true);

// More generalAssert.StartsWith("", result, StringComparison.OrdinalIgnoreCase);Assert.EndsWith("", result);Assert.Contains("abc", result, StringComparison.OrdinalIgnoreCase);

List/IEnumerable

// Result Type var okResult = Assert.IsType(result.Result);

// Values var items = Assert.IsAssignableFrom(okResult.Value);

// Count Assert.Equal(3, items.Count());

// Properties Assert.Equal(1, items.ElementAt(0).ID); Assert.Equal(2, items.ElementAt(1).ID); Assert.Equal(3, items.ElementAt(2).ID);

// Contains/ DoesNotContainAssert.Contains(expectedItem, result);

// AllAssert.All(collection, item => Assert.NotNull(item))

// EqualAssert.Equal(expectedCollection, actualCollection)

Range

// InRangeAssert.InRange(customer.Age, 25, 40)

Error Messages

// Bad Requestvar badRequestResult = Assert.IsType(result);Assert.Equal("ProductModelId should be in the format '123456'", badRequestResult.Value);

Interface implementations

var items = Assert.IsAssignableFrom(okResult.Value);

Exceptions

// Throw exceptionAssert.Throws(result);

Assert.Throws( ()=> customer.GetOrdersByName(null));

// Check exception messageAssert.Equal("Hello", exception.Message);

Check whether mock methods were called, using VerifyAll() and VerifyNoOtherCalls()

_objectMock.VerifyAll();_objectMock.VerifyNoOtherCalls();_objectMock.VerifyAll()

This method verifies that all expected interactions with the mocked object have occurred.

In other words, it ensures that all the methods that were set up with expectations (such as Setup calls in Moq) have been called during the test. If any of the expected interactions did not occur, this method typically throws an exception, indicating a test failure.

2. _objectMock.VerifyNoOtherCalls()

This method verifies that there were no additional unexpected calls made to the mocked object beyond those explicitly set up with expectations.

This may sound irrelevant but it is not. Other calls can simply cause side effects which is often the main cause of production issues

It's used to ensure that the test is not calling any methods on the mocked object that it shouldn't be. If there are unexpected calls, this method also typically throws an exception, indicating a test failure.

Complete Assert List

using Xunit;using System;

public class MyTests{ [Fact] public void EqualityAssertions() { // Equality Assertions Assert.Equal(expected, actual); Assert.NotEqual(notExpected, actual); Assert.StrictEqual(expected, actual); Assert.NotStrictEqual(notExpected, actual); }

[Fact] public void NullityAssertions() { // Nullity Assertions Assert.Null(obj); Assert.NotNull(obj); }

[Fact] public void BooleanAssertions() { // Boolean Assertions Assert.True(condition); Assert.False(condition); }

[Fact] public void TypeAssertions() { // Type Assertions Assert.IsType(expectedType, obj); Assert.IsNotType(unexpectedType, obj); Assert.IsAssignableFrom(baseType, obj); }

[Fact] public void ComparisonAssertions() { // Comparison Assertions Assert.InRange(value, low, high); Assert.NotInRange(value, low, high); }

[Fact] public void CollectionAssertions() { // Collection Assertions Assert.Contains(expected, collection); Assert.DoesNotContain(unexpected, collection); Assert.Empty(collection); Assert.NotEmpty(collection); Assert.Single(collection); Assert.All(collection, condition); }

[Fact] public void ExceptionAssertions() { // Exception Assertions Assert.Throws(() => SomeMethod()); Assert.ThrowsAny(() => SomeMethod()); Assert.ThrowsAsync(async () => await SomeMethodAsync()); Assert.ThrowsAnyAsync(async () => await SomeMethodAsync()); Assert.DoesNotThrow(() => SomeMethod()); }

[Fact] public void StringAssertions() { // String Assertions Assert.StartsWith(expectedSubString, actual); Assert.EndsWith(expectedSubString, actual); Assert.Contains(subString, actual); Assert.Matches(pattern, actual); Assert.DoesNotMatch(pattern, actual); }

[Fact] public void MiscellaneousAssertions() { // Miscellaneous Assertions Assert.Same(expected, actual); Assert.NotSame(notExpected, actual); Assert.Empty(strOrCollection); Assert.NotEmpty(strOrCollection); Assert.Equal(expected, actual); }}

8. Running Test

Understand how xUnit runs your test methods

Upon clicking run tests, xUnit will create an instance of the test class for every method test being executed.

Manually

Use the command line

dotnet test

Or right click your test class and click “run tests”

A nice GUI (Test explorer) with all your tests will appear

You can also access this GUI using the Test icon on the top in VS

Automatically with a pipeline

Now, once you have finished running your tests locally, you want to make sure that any time new code is being pushed, that the tests are always being run.

You will want to do this automatically, and this where pipelines shine!

I suggest you check my other blog on how to do that :)

.NET Build YAML Pipeline with Code Coverage Report in Azure DevOpsThis article shows how to create a .net build pipeline in YAML that also creates a code coverage report in Azure DevOps

medium.com

What is code coverage?

So you’ve run all your tests and you think, job’s done!

Well, not so fast, first thing you want to ensure is that the tests cover most of your code.

This is referred to as code coverage.

A code coverage report will give you how much percent of your code has been tested, whilst 100% is nice, a typical project aims for 80% code coverage

Use code coverage for unit testing — .NETLearn how to use the code coverage capabilities for .NET unit tests.

learn.microsoft.com

Code Coverage is available through Coverlet

Coverlet is a free and open-source tool, to use it install the following SDK to your test project

It should come by default with your xUnit project

In the command line in your test project run

dotnet test /p:CollectCoverage=true

You will then receive the following report

Again this is manual and I recommend using the test coverage in a pipeline as mentioned above in my other blog!

GitHub - coverlet-coverage/coverlet: Cross platform code coverage for .NETCross platform code coverage for .NET. Contribute to coverlet-coverage/coverlet development by creating an account on…

github.com

Determine code testing coverage — Visual Studio (Windows)Learn how to use the code coverage feature of Visual Studio to determine what proportion of your project code is being…

learn.microsoft.com

Debugging Tests

Just as any normal code, one can actually debug your test, which is really useful to understand what input, argument or any setup that is missing.

You can do so manually by setting up a breakpoint and then selecting the Debug button

Note that you will get into the class that is being tested once you instantiate the class object with the Mock objects (in the ACT phase)

Press F11 and you will land in the tested class



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3